﻿using System;
using System.Collections.Generic;
using System.Linq;

using ProjectBase.Core;

using $prjsn$.Common;
using $prjsn$.Common.Data;
using $prjsn$.Common.Validation;

namespace $safeprojectname$
{
    public abstract class EntityValidatorBase<TEntity, TID, TDao>
        : EntityValidatorBase<TEntity, TID>
        where TEntity : class, IPersistentObjectWithTypedId<TID>
        where TDao : class, IReadDao<TEntity, TID>
    {
        protected override IHaveMappingInfo<TEntity> GetMapper()
        {
            return Mapper;
        }

        public virtual TDao Dao { protected get; set; }

        public virtual IHaveMappingInfo<TEntity> Mapper { protected get; set; }
    }

    public abstract class EntityValidatorBase<TEntity, TID>
          : ValidatorBase<TEntity>
          , IEntityValidator<TEntity, TID>
          , IMetaDataProvider<TEntity, TID>
        where TEntity : class, IPersistentObjectWithTypedId<TID>
    {
        private readonly IList<string> _allwaysIgnoredProperties = new List<string> { "IDToDisplay", "Display" };
        private readonly IList<string> _allwaysNotEditableProperties = new List<string> { "DateModified", "DateCreated", "Children" };

        public virtual bool CanBeDeleted(TEntity entity)
        {
            // no item, no validation
            if (entity.IsNull())
            {
                return false;
            }

            var result = true;

            foreach (var rule in Rules.Select(ValidateOn.CanDelete, ApplyOn.Server))
            {
                ValidationMessage message;
                if (rule.Validate(entity, out message).Equals(ValidationResult.IsValid))
                {
                    continue; // OK
                }

                result = false; // Error
                if (rule.BreakOnError)
                {
                    break;
                }
            }
            return result;
        }

        public virtual bool CanBeEdited(TEntity entity)
        {
            // no item, no validation
            if (entity.IsNull())
            {
                return false;
            }

            var result = true;

            foreach (var rule in Rules.Select(ValidateOn.CanEdit, ApplyOn.Server))
            {
                ValidationMessage message;
                if (rule.Validate(entity, out message).Equals(ValidationResult.IsValid))
                {
                    continue; // OK
                }

                result = false; // Error
                if (rule.BreakOnError)
                {
                    break;
                }
            }
            return result;
        }

        public virtual MetaDataMessage GetMetaData()
        {
            var metaData = MetaData;
            if (metaData.IsNull())
            {
                lock (Locker)
                {
                    var mapper = GetMapper();
                    var map = mapper.Is() ? mapper.MappingInfo : null;

                    // the reflection to get a skeleton
                    metaData = new MetaDataReflector<TEntity, TID>().ReadMetaData(map, AllwaysIgnoredProperties);

                    // custome enrichment
                    EnrichMetaData(metaData);

                    MetaData = metaData;
                }
            }

            var result = metaData.Clone();

            return result;
        }

        protected virtual IHaveMappingInfo<TEntity> GetMapper()
        {
            return null;
        }

        public virtual MetaDataMessage GetMetaData(TID id)
        {
            return GetMetaData();
        }

        protected virtual void EnrichMetaData(MetaDataMessage metaData)
        {
            foreach (var attribute in metaData.Attributes)
            {
                EnrichMetaDataAttribute(attribute, metaData);
                EnrichMetaDataWithValidation(attribute, metaData);
            }
        }

        protected virtual void EnrichMetaDataAttribute(MDAttribute attribute, MetaDataMessage metaData)
        {
        }

        protected virtual void EnrichMetaDataWithValidation(MDAttribute attribute, MetaDataMessage metaData)
        {
            var rules = Rules.Where(r => r.PropertyName.IsEqual(attribute.Name));

            // no validation rules for Not editable field
            if (!attribute.CanEdit)
            {
                return;
            }

            if (AllwaysNotEditableProperties.Contains(attribute.Name))
            {
                attribute.CanEdit = false;
                return;
            }

            foreach (var rule in rules)
            {
                var validationRule = rule.GetMetaDataRule();
                if (validationRule.Is())
                {
                    attribute.ValidationRules.Add(validationRule);
                }
            }

            var shouldBeRequired = attribute.ValidationRules.IsEmpty()
                && !attribute.IsNullable;

            if (shouldBeRequired)
            {
                attribute.ValidationRules.Add(new MDMandatoryRule
                {
                    IsRequired = true,
                });
            }
        }

        #region Properties

        protected readonly object Locker = new object();

        protected virtual MetaDataMessage MetaData { get; set; }

        protected virtual IList<string> AllwaysIgnoredProperties
        {
            get { return _allwaysIgnoredProperties; }
        }

        protected virtual IList<string> AllwaysNotEditableProperties
        {
            get { return _allwaysNotEditableProperties; }
        }

        #endregion Properties
    }
}